﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Data;
using System.Linq;
using System.IO;
using ReBasic.Helper;
using ReBasic.Model;
using ReBasic.Database;
using System.Collections;

namespace ReBasic.Algorithm
{
    public class RelationshipExtractor
    {
        public event MainFrame.ProcessHandler DetailProcessEvent;
        public event MainFrame.TripleHandler ProcessEvent;
        public event MainFrame.InfoHandler StateEvent;

        StreamReader sr;
        StreamWriter sw;
        FileStream fsr, fsw;
        //ProBaseHeper pbh;
        TermExtractor te;
        ConcurrenceHelper ch;
        StringHelper sh;

        int attr_ent_cnt;

        void OnProcessEvent(int va, int vb, int perc)
        {
            if (ProcessEvent != null)
                ProcessEvent(va, vb, perc);
        }

        void OnStateEvent(string info, bool isOver)
        {
            if (StateEvent != null)
                StateEvent(info, isOver);
        }

        void OnDetailProcessEvent(int perc)
        {
            if (DetailProcessEvent != null)
                DetailProcessEvent(perc);
        }

        /// <summary>
        /// 打印Triple信息
        /// </summary>
        /// <param name="t"></param>
        private void writeTriple(Triple_hrt t)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(t.host_entity).Append('\t')
                .Append(t.relation).Append('\t')
                .Append(t.target_entity).Append('\t')
                .Append(t.fhrt).Append('\t')
                .Append(t.fhr).Append('\t')
                .Append(t.fht).Append('\t')
                .Append(t.frt).Append('\t')
                .Append(t.q);
                //.Append(t.q).Append('\t')
                //.Append(t.occPos);
            sw.WriteLine(sb.ToString());
        }

        /// <summary>
        /// Kernel step of relationship extracting
        /// </summary>
        /// <param name="attr">attr</param>
        /// <param name="host_entity">host entity</param>
        /// <param name="SubConDict">Sub-concept Dictionary </param>
        /// <param name="sh">StringHelper instance</param>
        void RelationshipExtraction(string attr, string host_entity, Dictionary<string, int> SubConDict, StringHelper sh)
        {
            #region Definition
            const int thres_sub_sup = 100;
            int p_sup, p_sub;
                //Set p_sup as the total frequency that a string is occurred as SUPER-CONCEPT.
                //Set p_sub as the total frequency that a string is occurred as SUB-CONCEPT.
                //the value p_sub/p_sup MUST BE HIGHER than this threshold.
            int ratio_host, ratio_target;       //ratio = p_sub/p_sup
            const int thres_fhrt = 2;
                //the total occurrences of h,r,t MUST BE HIGHER than this threshold.
            //int case_rt = 0, case_ht = 0, case_hr = 0;
            int i, useCnt = 0, tot = 0;
            List<string> host_entity_set;
            List<string> sent_r = new List<string>();
            List<int> sent_r_idx = new List<int>();         //Store the index of sentences, 1 to n
            List<Triple_hrt> hrt_set = new List<Triple_hrt>();
            HashSet<string> all_te_set = new HashSet<string>();
            #endregion
            #region Get host class
            string cls = GetClass(host_entity);
            if (cls == "NULL")
            {
                OnStateEvent("Host class is NULL.", false);
                return;
            }
            sw.WriteLine("CONCEPTS ----------No.{0} : PAIR<\t{1}\t{2}\t>----------", attr_ent_cnt, cls, attr);
            #endregion
            OnProcessEvent(0, 0, 0);
            host_entity_set = GetHostInstance(cls);
            //host_entity_set = get_host_instance_by_uniqueness(cls);
            sent_r = sh.SearchAllFile(attr, ref sent_r_idx);        
            
            ch.Initialize(host_entity_set, SubConDict);
            ch.GenerateSubMatrix(sent_r, sent_r_idx, attr, SubConDict);
            ch.GenerateTotalMatrix(sent_r_idx, SubConDict);
            Triple_hrt tp;
            #region Generate <h,r,t>
            for (i = 0; i < host_entity_set.Count; i++)
            {
                foreach (KeyValuePair<int, int> kvp in ch.SubMatrix[i])
                {
                    tot++;
                    #region Generate Candidate
                    tp = new Triple_hrt();
                    tp.host_entity = host_entity_set[i];
                    tp.relation = attr;
                    tp.target_entity = ch.FindString(kvp.Key);
                    tp.fhrt = ch.FindHRT(tp.host_entity, tp.target_entity, SubConDict);
                    tp.fhr = ch.FindHR(i);
                    tp.fht = ch.FindHT(tp.host_entity, tp.target_entity, SubConDict);
                    tp.frt = ch.FindRT(tp.target_entity, SubConDict);
                    #endregion
                    #region Get p_sup, p_sub for target entity
                    if (ProBase.SupConDict.ContainsKey(tp.target_entity))
                        p_sup = ProBaseInterface.SupFreq(tp.target_entity);
                    else
                        p_sup = 0;
                    p_sub = ProBaseInterface.SubFreq(tp.target_entity);
                    ratio_target = (p_sup == 0 ? 9999999 : p_sub / p_sup);
                    #endregion
                    #region Get p_sup, p_sub for host entity
                    if (ProBase.SupConDict.ContainsKey(tp.host_entity))
                        p_sup = ProBaseInterface.SupFreq(tp.host_entity);
                    else
                        p_sup = 0;
                    p_sub = ProBaseInterface.SubFreq(tp.host_entity);
                    ratio_host = (p_sup == 0 ? 9999999 : p_sub / p_sup);
                    #endregion
                    //We'll check the p_sub/p_sup for the retrieval host_entity & target_entity
                    //If it's not higher than our threshold, we'll induce that the entity is not good.
                    //So here we have two constraints:
                        //1. the freq(h,r,t) is large enough
                        //2. the ratio is large enough for both h and t.
                    if (tp.fhrt > thres_fhrt 
                        && ratio_host > thres_sub_sup
                        && ratio_target > thres_sub_sup)
                    {
                        useCnt++;
                        tp.CalcQ();
                        hrt_set.Add(tp);
                    }
                }
            }
            hrt_set.Sort();
            for (i = 0; i < hrt_set.Count; i++)
            {
                //hrt_set[i].occPos = ch.FindOccPos(
                //    hrt_set[i].host_entity, hrt_set[i].target_entity, SubConDict);
                writeTriple(hrt_set[i]);
            }
            sw.WriteLine("ENDS");
            #endregion
            OnStateEvent("\tOutput complete, with "
                + tot.ToString() + " h_r_t pairs, "
                + useCnt.ToString() + " used & output cases, "
                + "with percentage of" + (100 * useCnt / tot).ToString() + "%."
                //+ Math.Min(useCnt, TOP_M).ToString() + " output cases, "
                //+ case_hr.ToString() + " cases of HR, "
                //+ case_ht.ToString() + " cases of HT, "
                //+ case_rt.ToString() + " cases of RT."
                , true);
        }

        /// <summary>
        /// 通过host_entity得到class
        /// </summary>
        /// <param name="host_entity"></param>
        /// <returns></returns>
        private string GetClass(string host_entity)
        {
            //List<Relation> rel_set = pbh.FindSubConcept(host_entity, 1);
            List<IndexRelation> ixrel_set = ProBaseInterface.FindTopInSub(host_entity, 1);
            if (ixrel_set.Count == 0)
                return "NULL";
            return ProBase.SupUhash[ixrel_set[0].sup_idx];
        }

        /// <summary>
        /// 得到top-K的host instance, using TF-IDF
        /// </summary>
        /// <param name="host_class"></param>
        /// <returns></returns>
        private List<string> GetHostInstance(string host_class)
        {
            const int K = 50;
            int tot_freq, freq, cls_count;
            double d;
            //List<Relation> rel_set = pbh.FindSuperConcept(host_class, 2 * K);
            List<IndexRelation> ixrel_set = ProBaseInterface.FindTopInSup(host_class, 2 * K);
            List<string> res = new List<string>();
            List<SortInstance> srtList = new List<SortInstance>();
            if (ixrel_set.Count <= K)
            {
                foreach (IndexRelation r in ixrel_set)
                    res.Add(ProBase.SubUhash[r.sub_idx]);
            }
            else
            { 
                tot_freq = 0;
                foreach (IndexRelation r in ixrel_set)
                    tot_freq += r.freq;
                foreach (IndexRelation r in ixrel_set)
                {
                    freq = r.freq;
                    string sub = ProBase.SubUhash[r.sub_idx];
                    cls_count = ProBaseInterface.SubCount(sub);
                    d = Math.Log(1.0 * MainFrame.TotalSupConcept / cls_count) * freq / tot_freq;
                    srtList.Add(new SortInstance(sub, d));
                }
                srtList.Sort();
                int cnt = srtList.Count;
                for (int i = 0; i < K && i < cnt; i++)
                {
                    string s = srtList[i].instance;
                    res.Add(s);
                }
            }
            return res;
        }

        private List<string> get_host_instance_by_uniqueness(string host_class)
        {
            const int K = 50;
            const int top_in_sub = 100;
            List<IndexRelation> ixrel_set = ProBaseInterface.FindTopInSup(host_class, 2 * K);
            List<string> res = new List<string>();
            List<SortInstance> srtList = new List<SortInstance>();
            foreach (IndexRelation ixr in ixrel_set)
            { 
                int tar_idx = ixr.sub_idx;
                List<IndexRelation> sup_set = ProBaseInterface.FindTopInSub(tar_idx, top_in_sub);
                if (sup_set.Count == 0 || ProBase.SupUhash[sup_set[0].sup_idx] != host_class)
                    continue;
                string candidate = ProBase.SubUhash[tar_idx];
                double deno = 0, mole = 0;
                int total_freq = 0;
                for (int i = 0; i < sup_set.Count; i++)
                { 
                    int freq = sup_set[i].freq;
                    total_freq += freq;
                    deno += Math.Log(freq, 2) * freq;
                    if (i == 0) mole = deno;
                }
                mole = mole / deno;
                if (mole >= 0.3)           //I think it's a good entity
                {
                    mole = mole * (Math.Log(total_freq, 2));       //popular is better
                    srtList.Add(new SortInstance(candidate, mole));
                }
            }
            srtList.Sort();
            int cnt = srtList.Count;
            for (int i = 0; i < K && i < cnt; i++)
            {
                string s = srtList[i].instance;
                res.Add(s);
            }
            return res;
        }

        public void Work(Dictionary<string, int> SubConDict)
        {
            string str;
            int lmt = 1000;
            attr_ent_cnt = 0;

            te = new TermExtractor();
            sh = new StringHelper();
            ch = new ConcurrenceHelper();
            ch = new ConcurrenceHelper();

            sh.DetailProcessEvent += new MainFrame.ProcessHandler(OnDetailProcessEvent);
            sh.StateEvent += new MainFrame.InfoHandler(OnStateEvent);
            ch.ProcessEvent += new MainFrame.ProcessHandler(OnDetailProcessEvent);
            ch.StateEvent += new MainFrame.InfoHandler(OnStateEvent);
            
            fsr = new FileStream(MainFrame.SelAttrEntityPath, FileMode.Open);
            sr = new StreamReader(fsr, Encoding.UTF8);
            fsw = new FileStream(MainFrame.TriplePath, FileMode.Create);
            sw = new StreamWriter(fsw, Encoding.UTF8);
            //pbh.DataBind();
            OnProcessEvent(0, 0, 0);
            while (!sr.EndOfStream && attr_ent_cnt < lmt)
            {
                attr_ent_cnt++;
                str = sr.ReadLine();
                string[] spt = str.Split('\t');
                OnStateEvent("Dealing No." + attr_ent_cnt.ToString() + 
                    ":  <" + spt[0] + "," + spt[1] + ">", false);
                RelationshipExtraction(spt[0], spt[1], SubConDict, sh);
                OnStateEvent("------------------", false);
                GC.Collect();
            }
            //pbh.Terminate();
            sr.Close(); sw.Close();
            fsr.Close(); fsw.Close();
            OnStateEvent("Phase 2 complete.", true);
        }

        public void CloseFile()
        {
            if (sr != null)
            {
                sr.Close();
                fsr.Close();
            }
            if (sw != null)
            {
                sw.Close();
                fsw.Close();
            }
            OnStateEvent("Phase 2 stopped.", true);
        }

    }
}
